package co.edu.uniandes.sd.mundo;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.Observable;
import java.util.Timer;
import java.util.TimerTask;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.DHParameterSpec;
import javax.security.auth.x500.X500Principal;

import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;


public class Nodo extends Observable 
{

	public enum EstadoDescarga{LIBRE,EN_PROCESO,VERIFICANDO_FIRMA,TERMINADA,CANCELADA};
	
	public final static String COMANDO = "Comando";
	
	public final static String CONTENIDO = "Contenido";
	
	public final static String IDENTIFICADOR = "Identificador";
	
	public final static String PALABRAS_CLAVE="PalabrasClave"; 
	
	public final static String NOMBRE="Nombre";
	
	public final static String TAMANIO = "Tamanio";
	
	public final static String PIPE_INFO="PipeInfo";
	
	public final static String TIMESTAMP = "Timestamp";
	
	public final static String FIRMA_DIGITAL = "FirmaDigital";
	
	public final static String DUENO_ARCHIVO = "DuenoArchivo";
	
	public final static String MENSAJE = "mensaje";
	
	public final static String MAC = "mac";
	
	public final static String SOLICITUD_AUTENTICACION = "solicitud_autenticacion";
	
	public final static String AUTENTICACION = "autenticacion";
	
	public final static String LLAVE = "llave";
	
	public final static String NUM_CONSULTA = "numconsulta";
	
	public final static String DESCARGAR_ARCHIVO = "DescargarArchivo";
	
	public final static String ENVIAR_ARCHIVO = "EnviarArchivo";
	
	public final static String ARCHIVOS_CON_PALABRA_CLAVE = "ArchivosConPalabraClave";
	
	public final static String RESPUESTA_PALABRA_CLAVE = "RespuestaPalabraClave";
	
	public final static String ARCHIVO_MAS_PARECIDO = "ArchivoMasParecido";
	
	public final static String RESPUESTA_ARCHIVO_MAS_PARECIDO = "RespuestaArchivoMasParecido";

	public final static String PEDIR_PERMISO = "PedirPermiso";
	
	public final static String CONCEDER_PERMISO = "ConcederPermiso";
	
	public final static String OBTENER_CERTIFICADO_DIGITAL = "ObtenerCertificadoDigital";
	
	public final static String CERTIFICADO_DIGITAL = "CertificadoDigital";
	
	public final static long TIEMPO_PUBLICACION_ADVERTISEMENT_MODIFICACION = 50000;
	
	public final static long TTL_ADVERTISEMENT_MODIFICACION = 55000;
	
	public final static long TIMEOUT_RESULTADOS = 2000;
	
	public final static String GUARDAR_ESTADO = "GuardarEstado";
	
	public final static String RESPUESTA_GUARDAR_ESTADO = "RespuestaGuardarEstado";

	public final static String ALGORITMO_ENCRIPCION_ASIMETRICO = "RSA";
	
	public final static String ALGORITMO_GENERACION_FIRMA_DIGITAL = "SHA256withRSA";
	
	public final static long TIEMPO_VIDA_CERTIFICADO = 300000;

	public static final String ALGORITMO_GENERACION_LLAVES = "DH";

	public static final String ALGORITMO_SIMETRICO = "DESede";
	
	public static final String ALGORITMO_MAC = "HmacMD5";
	
	private Hashtable<String,Archivo> tablaArchivos;
	
	private ArrayList<Archivo> archivos;
	
	private ArrayList<String> archivosBusqueda;
	
	private File carpetaCompartida;
	
	private String nombre;

	private ComunicacionesNodo comunicacion;
	
	private ArchivoTamanio archivoTamanio;
	
	private ModificacionArchivo modificacion;
	
	private Timer timer;
	
	private ChequeoResultados chequeoResultados;
	
	private PublicacionAdvertisement publicacionAdvertisement;
	
	private Estado estadoNodo;
	
	private Hashtable<String, Estado> estadosNodos;

	private PrivateKey llavePrivada;
	
	private PublicKey llavePublica;
	
	private PrivateKey llavePrivadaEncripcionSimetrica;
	
	private PublicKey llavePublicaEncripcionSimetrica;
	
	private Signature firmaDigital;
	
	private X509Certificate certificadoDigital;
	
	private EstadoDescarga estadoDescarga;
	
	private Archivo descargaEnProgreso;
	
	private String login;
	
	private String pass;
	
	private SolicitadorDeCertificados solicitador;
	
	public Nodo(String user, String pass) throws Exception
	{
		nombre = "Nodo"+System.currentTimeMillis();
		this.login = user;
		this.pass = pass;
		tablaArchivos = new Hashtable<String, Archivo>();
		archivos = new ArrayList<Archivo>();
		archivosBusqueda = new ArrayList<String>();
		comunicacion = new ComunicacionesNodo(this);
		carpetaCompartida = new File("./carpetaCompartida/"+nombre+"/");
		carpetaCompartida.mkdirs();
		File f = new File("./carpetaCompartida/"+nombre+"/texto/");
		f.mkdirs();
		f = new File("./carpetaCompartida/"+nombre+"/audio/");
		f.mkdirs();
		f = new File("./carpetaCompartida/"+nombre+"/video/");
		f.mkdirs();
		timer = new Timer();
		chequeoResultados = new ChequeoResultados();
		estadoNodo = new Estado();
		estadosNodos = new Hashtable<String, Estado>();
		estadoDescarga = EstadoDescarga.LIBRE;
		
		
		// Seguridad
		try{
			KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITMO_ENCRIPCION_ASIMETRICO);
			int bitLength = 1024;
			generator.initialize(bitLength);
			
			KeyPair keypair = generator.generateKeyPair();
			llavePrivada = keypair.getPrivate();
			llavePublica = keypair.getPublic();
			firmaDigital = Signature.getInstance(ALGORITMO_GENERACION_FIRMA_DIGITAL);
			solicitador = new SolicitadorDeCertificados(this.login, this.pass, llavePrivada, llavePublica);
			certificadoDigital = expedirCertificadoDigital();
			
			generator = KeyPairGenerator.getInstance(ALGORITMO_GENERACION_LLAVES);
			
			generator.initialize(bitLength);
			keypair = generator.generateKeyPair();
			llavePublicaEncripcionSimetrica = keypair.getPublic();
			llavePrivadaEncripcionSimetrica = keypair.getPrivate();
			
		}catch (Exception e) {
			throw new Exception("Error inicializando la seguridad de la aplicación",e);
		}
		
	}
	
			
	public void agregarArchivo(File f, String identificador, ArrayList<String> palabrasClave)throws Exception{
		Date tiempo = obtenerTiempoActual();
		File f2 = almacenarArchivo(f.getName(), new FileInputStream(f));
		byte[] firma = firmarArchivo(f2);
		f2 = encriptarArchivo(f2);
		Archivo archivo = new Archivo(palabrasClave,identificador,tiempo,Archivo.TipoArchivo.Generado,f2);
		
		
		archivo.setFirma(firma);
		archivo.setDuenoArchivo(comunicacion.getInputPipe().getID().toString());
		archivos.add(archivo);
		tablaArchivos.put(archivo.getIdentificador(), archivo);
		comunicacion.refrescarAdvertisement(archivos);
		System.out.println("Archivo agregado correctamente a las "+tiempo);
		
		System.out.println("Verificacion firma!!!!:"+verificarFirma(f2, firma, llavePublica));
		refrescarArchivos();
	}
	

	public void refrescarArchivos() throws Exception{
		/*ArrayList<File> archivosARevisar = new ArrayList<File>();
		explorarCarpeta(carpetaCompartida,archivosARevisar);
		
		ArrayList<Archivo> nuevosArchivos = new ArrayList();
		for (File file : archivosARevisar) {
			Archivo archivo = tablaArchivos.get(file.getAbsolutePath());
			if(archivo == null){
				archivo = new Archivo(new ArrayList(), file.getAbsolutePath(), obtenerTiempoActual(), Archivo.TipoArchivo.Generado, file);	
			}
			nuevosArchivos.add(archivo);
		}
		
		archivos = nuevosArchivos;
		tablaArchivos.clear();
		for (Archivo archivo : nuevosArchivos) {
			tablaArchivos.put(archivo.getIdentificador(), archivo);
		}*/
		setChanged();
		notifyObservers(archivos);
	}
	
	private void explorarCarpeta(File carpeta,ArrayList archivos){
		File[] hijos = carpeta.listFiles(new FiltroArchivos());
		for (File file : hijos) {
			if(file.isDirectory()){
				explorarCarpeta(file, archivos);
			}else{
				archivos.add(file);
			}
		}
		
	}
	
	public void descargarArchivoPorIdentificador(String identificador){
		estadoDescarga = EstadoDescarga.EN_PROCESO;
		estadoNodo.agregarEstadoDescarga(identificador+";INICIADA");
		comunicacion.buscarPorIdentificador(identificador);
		setChanged();
		notifyObservers("DESCARGA");		
	}
	
	public Date obtenerTiempoActual() throws Exception{
		return SntpUtil.darHoraOficial();
	}
	
	public void enviarArchivo(String identificador, String pipeId)throws Exception{
		Archivo archivo = tablaArchivos.get(identificador);
		comunicacion.enviarArchivo(archivo, pipeId);
		
	}
	
	public void buscarArchivoPorPalabrasClave(String palabrasClave){
		comunicacion.buscarPorPalabraClave(palabrasClave);
	}
	
	public void buscarArchivoPorTamanio(long tamanio){
		archivoTamanio = new ArchivoTamanio(tamanio, -1, "");
		comunicacion.buscarArchivoPorTamanio(tamanio);
		
	}
	
	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	
	public void responderArchivosConPalabraClave(String palabraClave, String pipeId) throws Exception{
		String contenido = "";
		for (Archivo archivo : archivos) {
			System.out.println("aid:"+archivo.getIdentificador());
			ArrayList<String> palabrasClave = archivo.getPalabrasClave();
			for (String string : palabrasClave) {
				if(palabraClave.equals(string)){
					contenido+=archivo.getIdentificador()+":"+archivo.getArchivo().getName()+";";
					break;
				}
			}
		}
		comunicacion.responderArchivosConPalabraClave(contenido, pipeId);
	}
	
	public void responderArchivoMasParecido(long tamanio, String pipeId)throws Exception{
		long dif = Long.MAX_VALUE;
		String identificador = null;
		long tam=0;
		for (Archivo archivo : archivos) {
			File f = archivo.getArchivo();
			long dif2 = Math.abs(tamanio-f.length());
			if(dif2 < dif){
				identificador = archivo.getIdentificador();
				dif = dif2;
				tam = f.length();
			}
		}
		if(identificador!=null){			
			comunicacion.responderArchivoMasParecido(identificador,tam, pipeId);
		}
		
	}
	
	public void almacenarArchivoDescargado(String nombre,String identificador, String palabrasClave,InputStream is,InputStream firma,String dueno) throws Exception{
		System.out.println("Entrando a almacenarArchivoDescargado()");
		File file = almacenarArchivo(nombre, is);
		ArrayList<String> pClave = new ArrayList<String>(); 
		String[] p = palabrasClave.split(",");
		for (int i = 0; i < p.length; i++) {
			pClave.add(p[i]);
		}
		
		Archivo archivo = new Archivo(pClave,identificador, SntpUtil.darHoraOficial(),Archivo.TipoArchivo.Descargado,file);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int byt = firma.read();
		while(byt>=0){
			baos.write(byt);
			byt = firma.read();
		}
		baos.close();
		
		archivo.setFirma(baos.toByteArray());
		archivo.setDuenoArchivo(dueno);
		
		estadoDescarga = EstadoDescarga.VERIFICANDO_FIRMA;
		descargaEnProgreso = archivo;
		comunicacion.obtenerLlavePublica(archivo.getDuenoArchivo());
		setChanged();
		notifyObservers("DESCARGA");
		
	}
	
	public void verificarArchivo(InputStream firma)throws Exception{
		System.out.println("Estoy verificando");
		ObjectInputStream ois = new ObjectInputStream(firma);
		X509Certificate cert = (X509Certificate)ois.readObject();
		ois.close();
		PublicKey pk = cert.getPublicKey();
		System.out.println("Llave publica!!:"+pk.toString());
		boolean resultado = verificarFirma(descargaEnProgreso.getArchivo(), descargaEnProgreso.getFirma(), pk);
		
		if(resultado){			
			archivos.add(descargaEnProgreso);
			tablaArchivos.put(descargaEnProgreso.getIdentificador(), descargaEnProgreso);
			comunicacion.refrescarAdvertisement(archivos);
			estadoNodo.eliminarEstadoDescarga(descargaEnProgreso.getIdentificador());
			refrescarArchivos();
			System.out.println("Archivo almacenado correctamente a las "+SntpUtil.darHoraOficial());
			estadoDescarga = EstadoDescarga.TERMINADA;
		}else{
			System.out.println("Error verificando la firma");
			estadoDescarga = EstadoDescarga.CANCELADA;
		}
		
		setChanged();
		notifyObservers("DESCARGA");
		
	}
	
	private File almacenarArchivo(String nombre,InputStream is) throws Exception{
		String path = carpetaCompartida.getAbsolutePath();
		if(nombre.endsWith(".mpg")|| nombre.endsWith(".avi")||nombre.endsWith(".mpeg")){
			// Archivo de video
			path+="/videos/";
		}
		if(nombre.endsWith(".mp3")|| nombre.endsWith(".wav")||nombre.endsWith(".wma")){
			// Archivo de audio
			path+="/audio/";
		}
		if(nombre.endsWith(".pdf")|| nombre.endsWith(".doc")||nombre.endsWith(".docx")||nombre.endsWith(".txt")){
			// Archivo de texto
			path+="/texto/";
		}
		File file = new File(path+"/"+nombre);
		int j = 1;
		int index = nombre.indexOf(".");
		while(file.exists()){
			//Rename the file if it exists
			file = new File(path+"/"+nombre.substring(0, index)+j+nombre.substring(index));
			j++;
		}
		OutputStream os = new FileOutputStream(file);
		int info = is.read();
		while(info!=-1){
			os.write(info);
			info = is.read();
		}
		os.close();
		return file;
	}

	public File encriptarArchivo(File file) throws IOException, Exception 
	{

		byte[] bytesLlave = (llavePublicaEncripcionSimetrica.getEncoded());
		KeyAgreement ka = KeyAgreement.getInstance(ALGORITMO_GENERACION_LLAVES);
		ka.init(llavePrivadaEncripcionSimetrica);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytesLlave);
		KeyFactory keyFact = KeyFactory.getInstance(ALGORITMO_GENERACION_LLAVES);
		ka.doPhase(keyFact.generatePublic(keySpec), true);
		Key key = ka.generateSecret(ALGORITMO_SIMETRICO);
        byte[] fileData = encriptar(getBytesFromFile(file), key);
		FileOutputStream fos = new FileOutputStream(file);
		fos.write(fileData);
		fos.close();
		return file;
	}
	
	public File desEncriptarArchivo(File file) throws IOException, Exception 
	{
		byte[] tmp = (llavePublicaEncripcionSimetrica.getEncoded());
		KeyAgreement ka = KeyAgreement.getInstance(Nodo.ALGORITMO_GENERACION_LLAVES);
		ka.init(getLlavePrivadaEncripcionSimetrica());
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(tmp);
		KeyFactory keyFact = KeyFactory.getInstance(Nodo.ALGORITMO_GENERACION_LLAVES);
		ka.doPhase(keyFact.generatePublic(keySpec), true);
		byte[] fileData = desencriptar(getBytesFromFile(file), ka.generateSecret(Nodo.ALGORITMO_SIMETRICO));
		FileOutputStream fos = new FileOutputStream(file);
		fos.write(fileData);
		fos.close();
		return file;
	}
	
	public static byte[] getBytesFromFile(File file) throws IOException {
        InputStream is = new FileInputStream(file);
    
        // Obtener el tamaño del archivo
        long length = file.length();
    
        if (length > Integer.MAX_VALUE) {
        }
    
        // Crear un arreglo de bytes para almacenar los datos
        byte[] bytes = new byte[(int)length];
    
        // Leer
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }
    
        // Asegurarse de haber leido todo el contenido
        if (offset < bytes.length) {
            throw new IOException("No se pudo leer completamente el archivo "+file.getName());
        }
    
        // Cerrar el stream y retornar el contenido en bytes
        is.close();
        return bytes;
    }

	public ArrayList<Archivo> darArchivos() { 
		return archivos;
	}

	public ArrayList<String> darArchivosBusqueda()
	{
		return archivosBusqueda;
	}

	public void agregarRespuestas(String archivos2) 
	{
		String [] rtas = archivos2.split(";");
		for(int i=0; i<rtas.length; i++)
		{
			System.out.println("blah:"+rtas[i]);
			archivosBusqueda.add(rtas[i]);
		}
		setChanged();
		notifyObservers(archivosBusqueda);
	}
	
	public synchronized void procesarRespuestaTamanio(String identificador, long tamanio){
		System.out.println("procese");
		if(archivoTamanio.compararTamanios(tamanio)){
			archivoTamanio.setIdentificador(identificador);
			archivoTamanio.setTamanio(tamanio);
			System.out.println("cambie");
			setChanged();
			notifyObservers(archivoTamanio);
		}
	}
	
	public class ArchivoTamanio{
		
		private long tamanioOriginal;
		
		private long tamanio;
		
		private String identificador;

		public ArchivoTamanio(long tamanioOriginal, long tamanio,
				String identificador) {
			super();
			this.tamanioOriginal = tamanioOriginal;
			this.tamanio = tamanio;
			this.identificador = identificador;
		}

		public long getTamanio() {
			return tamanio;
		}

		public void setTamanio(long tamanio) {
			this.tamanio = tamanio;
		}

		public String getIdentificador() {
			return identificador;
		}

		public void setIdentificador(String identificador) {
			this.identificador = identificador;
		}
		
		/**
		 * Compara el tama�o que viene por parametro con el tama�o actual a comparar.
		 * Si el tama�o que viene por partametro esta mas cerca al original retorna true, en los demas casos retorna false.
		 * @param tam
		 * @return
		 */
		public boolean compararTamanios(long tam){
			return tamanio < 0 || Math.abs(tamanioOriginal-tam)<Math.abs(tamanioOriginal-tamanio);
		}
		
	}

	public void responderGuardarEstado(String pipeInfo) throws Exception
	{ 
		File archivoEstado = guardarEstado();
		comunicacion.responderGuardarEstado(pipeInfo, archivoEstado);
		
		//SE SOLICITA A LOS NODOS QUE ESTAN EN idsNodos QUE GUARDEN SU ESTADO.
		//SE DEBE VERIFICAR QUE AUN NO SE HAYA AGREGADO A LA HASH
		//TODO
		comunicacion.enviarGuardarEstado();		
	}
	
	public void guardarEstadoAccion() throws Exception
	{
		guardarEstado();
		comunicacion.enviarGuardarEstado();
	}

	private File guardarEstado() throws Exception
	{
		long tiempo = System.currentTimeMillis();
		File folder = new File("stateFolder/"+nombre);
		if(!folder.exists())
		{
			folder.mkdir();
		}
		String ruta = "statefolder/"+nombre+"/estadoActual-"+nombre+"-"+tiempo;
		System.out.println(ruta);
		File archivoEstado = new File(ruta);
		File archivoEstadoTxt = new File(ruta+".txt");
		
		if(!estadoNodo.estaGuardado())
		{
			for(int i=0; i<archivos.size(); i++)
			{
				Archivo a = archivos.get(i);
				String estadoArchivo = a.getIdentificador()+";"+a.getTipoArchivo();
				estadoNodo.agregarEstadoArchivo(estadoArchivo);
			}
			if(modificacion!=null)
			{
				estadoNodo.agregarToken(modificacion);
			}
			
			estadoNodo.guardar(tiempo);
			PrintWriter pw = new PrintWriter(archivoEstadoTxt);
			String estado = estadoNodo.toString();
			String [] estadoLns = estado.split("\n");
			for(int i=0; i<estadoLns.length; i++)
			{
				pw.println(estadoLns[i]);
			}
			pw.close();
			FileOutputStream fos = new FileOutputStream(ruta);
			ObjectOutputStream out = new ObjectOutputStream(fos);
			out.writeObject(estadoNodo);
			out.close();
		}
		else
		{
			ruta = "stateFolder/"+nombre+"/estadoActual-"+nombre+"-"+estadoNodo.darTiempo(); 
			archivoEstado = new File(ruta);
		}
		
		
		return archivoEstado;
	}

	public void procesarRespuestaGuardarEstado(String idNodo, String pipeInfo, InputStream is) throws Exception 
	{
		//SE PROCESA LA RESPUESTA DE UNA PETICION DE GUARDAR ESTADO
		//EN LA RESPUESTA SE RECIBE EL ESTADO DE UN NODO, EL CUAL SE DEBE GUARDAR EN LA LISTA DE ESTADOS QUE SE TIENE
		File folder = new File("stateFolder/"+idNodo+"/others/");
		if(!folder.exists())
		{
			folder.mkdir();
		}
		File archSerializado = new File("stateFolder/"+idNodo+"/others/estadoActual-"+idNodo);
		OutputStream os = new FileOutputStream(archSerializado);
		int info = is.read();
		while(info!=-1){
			os.write(info);
			info = is.read();
		}
		os.close();
		FileInputStream fis = new FileInputStream(archSerializado);
		ObjectInputStream in = new ObjectInputStream(fis);
		Estado estadoNodo = (Estado)in.readObject();
		estadosNodos.put(idNodo, estadoNodo);
	}
	
	public void solicitarModificacionArchivo(String identificador)throws Exception{
		modificacion = new ModificacionArchivo(obtenerTiempoActual().getTime(),identificador);
		comunicacion.publicarAdvertisementModificacion(modificacion.getTimestamp(),identificador);
		comunicacion.buscarNodosModificacion(identificador,modificacion.getTimestamp());
		chequeoResultados = new ChequeoResultados();
		timer.schedule(chequeoResultados, TIMEOUT_RESULTADOS);
		publicacionAdvertisement = new PublicacionAdvertisement();
		timer.scheduleAtFixedRate(publicacionAdvertisement, 1000, TIEMPO_PUBLICACION_ADVERTISEMENT_MODIFICACION);
		setChanged();
		notifyObservers("INICIO_MODIFICACION");
	}
	
	/**
	 * Procesa una solicitud de permiso sobre la modificacion de un archivo,
	 * si no esta modificando el archivo o el tiempo de la modificacion es previo
	 * al de este nodo entonces concede el permiso.
	 * @param identificador
	 * @param timestamp
	 * @param pipeId
	 * @throws Exception
	 */
	public void solicitarPermiso(String identificador, long timestamp,String pipeId) throws Exception{
		if (modificacion == null || !modificacion.getIdentificadorArchivo().equals(identificador) || modificacion.pedirPermiso(pipeId, timestamp)){
			comunicacion.darPermiso(identificador, pipeId);
		}
	}
	
	public void registrarPermiso(String identificador, String pipeId){
		System.out.println("Registrar permiso->"+identificador);
		if (modificacion != null && modificacion.getIdentificadorArchivo().equals(identificador)){
			modificacion.confirmarPermiso(pipeId);
			if(modificacion.getEstadoModificacion() == ModificacionArchivo.EstadoModificacion.EN_CURSO){
				setChanged();
				notifyObservers("OBTENER_PERMISOS");
			}
		}
	}
	
	public void agregarNodoConflictoModificacion(String identificador, String pipeInfo){
		if (modificacion.getIdentificadorArchivo().equals(identificador)){
			modificacion.agregarNodoEnConflicto(pipeInfo);	
		}
	}
	
	public void finalizarModificacion() throws Exception{
		publicacionAdvertisement.cancel();
		ArrayList<String> pendientes = modificacion.getNodosPendientesPorAvisar();
		for (String pipeInfo : pendientes) {
			comunicacion.darPermiso(modificacion.getIdentificadorArchivo(), pipeInfo);
		}
		modificacion= null;
		setChanged();
		notifyObservers("FIN_MODIFICACION");
	}
	
	public void revisarEstadoModificacion(){
		if(modificacion==null) return;
		if (modificacion.getNodosEnConflicto().size()==0){
			modificacion.setEstadoModificacion(ModificacionArchivo.EstadoModificacion.EN_CURSO);
			setChanged();
			notifyObservers("OBTENER_PERMISOS");
		}
	}
	
	public byte[] firmarArchivo(File f) throws Exception{
		byte[] firma=null;
		firmaDigital.initSign(llavePrivada);
		FileInputStream fis = new FileInputStream(f);
		BufferedInputStream bufin = new BufferedInputStream(fis);
		byte[ ] buffer = new byte[1024];
		int len;
		while (bufin.available() != 0) {
			len = bufin.read(buffer);
			firmaDigital.update(buffer, 0, len);
		};
		bufin.close();
		firma = firmaDigital.sign();
		
		return firma;
	}
	
	public boolean verificarFirma(File archivo, byte[] firma, PublicKey llavePub) throws Exception{
		firmaDigital.initVerify(llavePub);
		FileInputStream datafis = new
		FileInputStream(archivo);
		BufferedInputStream bufin = new BufferedInputStream(datafis);
		byte[ ] buffer = new byte[1024];
		int len;
		while (bufin.available() != 0) {
			len = bufin.read(buffer);
			firmaDigital.update(buffer, 0, len);
		};
		bufin.close();
		return firmaDigital.verify(firma);
	}
	
	public void enviarCertificadoDigital(String pipeInfo)throws Exception {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bos);
		out.writeObject(certificadoDigital);
		out.close();
		System.out.println("Llave publica!"+llavePublica.toString());
		comunicacion.enviarCertificadoDigital(bos.toByteArray(), pipeInfo);
		
	}
	

	
	public X509Certificate expedirCertificadoDigital()throws Exception{
		return solicitador.darCertificado();
	}
	
	public byte[] getCertificadoDigitalTransmision() throws Exception{
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bos);
		out.writeObject(certificadoDigital);
		out.close();
		return bos.toByteArray();
	}
	
	public byte[] encriptar(byte[] objeto,Key llave) throws Exception{
		Cipher desCipher = Cipher.getInstance(ALGORITMO_SIMETRICO);
		desCipher.init (Cipher.ENCRYPT_MODE,
				llave);
		return desCipher.doFinal(objeto);
	}
	
	public byte[] desencriptar(byte[] objeto,Key llave)throws Exception{
		Cipher desCipher = Cipher.getInstance(ALGORITMO_SIMETRICO);
		desCipher.init (Cipher.DECRYPT_MODE,
				llave);
		return desCipher.doFinal(objeto);
	}
	
	public PrivateKey getLlavePrivada(){
		return llavePrivada;
	}
	
	public EstadoDescarga darEstadoDescarga(){
		return estadoDescarga;
	}
	
	
	
	public PrivateKey getLlavePrivadaEncripcionSimetrica() {
		return llavePrivadaEncripcionSimetrica;
	}

	public void validarConsulta(int n, X509Certificate cert)throws Exception{
		cert.checkValidity();
		cert.verify(cert.getPublicKey());
	}
	
	class ChequeoResultados extends TimerTask{
		@Override
		public void run() {
			revisarEstadoModificacion();
		}
	}
	
	class PublicacionAdvertisement extends TimerTask{
		@Override
		public void run() {
			comunicacion.publicarAdvertisementModificacion(modificacion.getTimestamp(), modificacion.getIdentificadorArchivo());
		}
	}

	public void enviarAutenticacion(String pipeInfo, int num)throws Exception {
		byte[] llavePublicaDH = llavePublicaEncripcionSimetrica.getEncoded();
		
		comunicacion.enviarAutenticacion(new ByteArrayInputStream(getCertificadoDigitalTransmision()), new ByteArrayInputStream(llavePublicaDH), num, pipeInfo);
		
	}

	public void manejarAutenticacion(InputStream certificado,
			InputStream llave2, int num, String pipeInfo) throws Exception{
		
		byte[] bytesLlave = parseStreamToBytes(llave2);
		KeyAgreement ka = KeyAgreement.getInstance(ALGORITMO_GENERACION_LLAVES);
		ka.init(llavePrivadaEncripcionSimetrica);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytesLlave);
		KeyFactory keyFact = KeyFactory.getInstance(ALGORITMO_GENERACION_LLAVES);
		Key DHkey = keyFact.generatePublic(keySpec);
		ka.doPhase(DHkey, true);
		Key key = ka.generateSecret(ALGORITMO_SIMETRICO);
		comunicacion.envioMensaje(num, key, pipeInfo, new ByteArrayInputStream(llavePublicaEncripcionSimetrica.getEncoded()),DHkey);
		
	}

	private byte[] parseStreamToBytes(InputStream is)throws Exception{
		ByteArrayOutputStream bois = new ByteArrayOutputStream();
		int byt = is.read();
		while(byt >=0){
			bois.write(byt);
			byt = is.read();
		}
		bois.close();
		return bois.toByteArray(); 
		
	}

	public void setLoginInfo(String user, String pass2) 
	{
		login = user;
		pass = pass2;
	}
	
	public byte[] generarMAC(byte[] data,Key key)throws Exception{
		KeyAgreement ka = KeyAgreement.getInstance("DH");
		ka.init(llavePrivadaEncripcionSimetrica);
		ka.doPhase(key, true);
		Key sk = ka.generateSecret(ALGORITMO_SIMETRICO);
		
		Mac mac = Mac.getInstance("HmacMD5");
		mac.init(sk);
		byte [ ] elmac = mac.doFinal(data);
		return elmac;
	}
	
	public static boolean verificarMAC (byte[] mac1, byte[] mac2) 
	{ 
		for (int i=0; i< mac1.length-1 ; i++)
		{ 
			if ( mac1[i] != mac2[i] ) 
				return false; 
		}
		return true; 
	}

	
}
